﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Whe;
using WOS;
using WOS.Common;

namespace WOS.Upload
{
    public enum UploadStatus
    {
        Stopped,
        Queued,
        Working,
        Finished,
        Failed
    }
    public enum UploadLinkStatus
    {
    }

    internal sealed class UploadItem : Item
    {
        #region Private Fields
        private UploadStatus status;

        private readonly List<UploadFile> files;
        private readonly List<UploadItemPoint> points;
        private readonly List<UploadItemLink> links;
        private readonly Dictionary<Pair, ITaskInfo> tasks;
        #endregion
        #region Constructors
        public UploadItem(Guid id, string name)
            : base(id, name)
        {
            this.files = new List<UploadFile>();
            this.points = new List<UploadItemPoint>();
            this.links = new List<UploadItemLink>();
            this.tasks = new Dictionary<Pair, ITaskInfo>();
        }
        #endregion

        #region Events
        public event EventHandler ProgressChanged;
        public event EventHandler StatusChanged;

        public event EventHandler<ObjectEventArgs<UploadFile>> FileAdded;
        public event EventHandler<ObjectEventArgs<UploadFile>> FileRemoved;
        public event EventHandler<ObjectEventArgs<UploadItemPoint>> PointAdded;
        public event EventHandler<ObjectEventArgs<UploadItemPoint>> PointRemoved;
        public event EventHandler<ObjectEventArgs<UploadItemLink>> LinkAdded;
        public event EventHandler<ObjectEventArgs<UploadItemLink>> LinkChanged;
        public event EventHandler<ObjectEventArgs<UploadItemLink>> LinkRemoved;
        #endregion
        #region Events' Raisers
        private void OnProgressChanged()
        {
            if (this.ProgressChanged != null)
                this.ProgressChanged(this, EventArgs.Empty);
        }

        private void OnFileAdded(UploadFile file)
        {
            if (this.FileAdded != null)
                this.FileAdded(this, new ObjectEventArgs<UploadFile>(file));
        }

        private void OnFileRemoved(UploadFile file)
        {
            if (this.FileRemoved != null)
                this.FileRemoved(this, new ObjectEventArgs<UploadFile>(file));
        }

        private void OnPointAdded(UploadItemPoint point)
        {
            if (this.PointAdded != null)
                this.PointAdded(this, new ObjectEventArgs<UploadItemPoint>(point));
        }

        private void OnPointRemoved(UploadItemPoint point)
        {
            if (this.PointRemoved != null)
                this.PointRemoved(this, new ObjectEventArgs<UploadItemPoint>(point));
        }

        private void OnLinkAdded(UploadItemLink link)
        {
            if (this.LinkAdded != null)
                this.LinkAdded(this, new ObjectEventArgs<UploadItemLink>(link));
        }

        private void OnLinkChanged(UploadItemLink link)
        {
            if (this.LinkChanged != null)
                this.LinkChanged(this, new ObjectEventArgs<UploadItemLink>(link));
        }

        private void OnLinkRemoved(UploadItemLink link)
        {
            if (this.LinkRemoved != null)
                this.LinkRemoved(this, new ObjectEventArgs<UploadItemLink>(link));
        }
        #endregion

        #region Public Members
        public override ItemType Type
        {
            get { return ItemType.Upload; }
        }

        public UploadStatus Status
        {
            get { return this.status; }
            set { this.status = value; }
        }

        public long Size
        {
            get
            {
                lock (this.files)
                {
                    var query = from file in this.files
                                select file.Size.GetBytes().Value;

                    return query.Sum();
                }
            }
        }

        public long WorkSize
        {
            get
            {
                lock (this.files)
                lock (this.points)
                {
                    var query = from file in this.files
                                from point in this.points
                                select file.Size.GetBytes().Value;

                    return query.Sum();
                }
            }
        }

        public long Progress
        {
            get
            {
                lock (this.files)
                lock (this.links)
                {
                    var query = from link in this.links
                                join file in this.files on link.File equals file.Id
                                select link.Download == null ? link.Progress : file.Size.GetBytes().Value;

                    return query.Sum();
                }
            }
        }

        #region Files
        public UploadFile GetFile(Guid id)
        {
            lock (this.files)
                return this.files.FirstOrDefault(x => x.Id == id);
        }

        public UploadFile[] GetFiles()
        {
            lock (this.files)
                return this.files.ToArray();
        }

        public void AddFile(UploadFile file)
        {
            lock (this.files)
            {
                this.files.Add(file);
                this.OnFileAdded(file);

                var links = from point in this.points
                            select new UploadItemLink(Guid.NewGuid(), file.Id, point.Id);

                foreach (var link in links)
                    this.AddLink(link);
            }
        }

        public void RemoveFile(Guid id)
        {
            lock (this.files)
            {
                var file = this.files.FirstOrDefault(x => x.Id == id);

                if (file != null && this.files.Remove(file))
                {
                    this.OnFileRemoved(file);

                    var links = from link in this.links
                                where link.File == id
                                select link;

                    foreach (var link in links.ToArray())
                        this.RemoveLink(link);
                }
            }
        }
        #endregion
        #region Points
        public UploadItemPoint GetPoint(Guid id)
        {
            lock (this.points)
                return this.points.FirstOrDefault(x => x.Id == id);
        }

        public UploadItemPoint[] GetPoints()
        {
            lock (this.points)
                return this.points.ToArray();
        }

        public void AddPoint(UploadItemPoint point)
        {
            lock (this.points)
            {
                this.points.Add(point);
                this.OnPointAdded(point);

                var links = from file in this.files
                            select new UploadItemLink(Guid.NewGuid(), file.Id, point.Id);

                foreach (var link in links)
                    this.AddLink(link);
            }
        }

        public void RemovePoint(Guid id)
        {
            lock (this.points)
            {
                var point = this.points.FirstOrDefault(x => x.Id == id);

                if (point != null && this.points.Remove(point))
                {
                    this.OnPointRemoved(point);

                    var links = from link in this.links
                                where link.Point == id
                                select link;

                    foreach (var link in links.ToArray())
                        this.RemoveLink(link);
                }
            }
        }
        #endregion
        #region Links
        public UploadItemLink GetLink(UploadFile file, UploadItemPoint point)
        {
            lock (this.links)
            {
                var query = from link in this.links
                            where link.File == file.Id && link.Point == point.Id
                            select link;

                return query.FirstOrDefault();
            }
        }

        public UploadItemLink[] GetLinks()
        {
            lock (this.links)
                return this.links.ToArray();
        }

        private void AddLink(UploadItemLink link)
        {
            lock (this.links)
            {
                this.links.Add(link);
                this.AttachEvents(link);
                this.OnLinkAdded(link);
            }
        }

        private void RemoveLink(UploadItemLink link)
        {
            lock (this.links)
            {
                if (this.links.Remove(link))
                {
                    this.DetachEvents(link);
                    this.OnLinkRemoved(link);
                }
            }
        }
        #endregion
        #region Tasks
        public ITaskInfo[] GetTasks()
        {
            lock (this.tasks)
                return this.tasks.Values.ToArray();
        }

        public void AddTask(UploadFile file, UploadItemPoint point, ITaskInfo task)
        {
            lock (this.tasks)
                if (!this.tasks.ContainsKey(new Pair { File = file, Point = point }))
                    this.tasks.Add(new Pair { File = file, Point = point }, task);
        }

        public void RemoveTask(ITaskInfo task)
        {
            lock (this.tasks)
                foreach (var entry in this.tasks.ToArray())
                    if (entry.Value.Equals(task))
                        this.tasks.Remove(entry.Key);
        }
        #endregion

        public bool IsUploading()
        {
            return this.status == UploadStatus.Working;
        }

        public bool IsWorking()
        {
            return this.status == UploadStatus.Working || this.status == UploadStatus.Queued;
        }
        #endregion
        #region Private Members
        private void AttachEvents(UploadItemLink link)
        {
            link.DeleteChanged += this.OnLinkChanged;
            link.DownloadChanged += this.OnLinkChanged;
            link.StatusChanged += this.OnLinkChanged;
            link.ProgressChanged += this.OnLinkProgressChanged;
        }

        private void DetachEvents(UploadItemLink link)
        {
            link.DeleteChanged -= this.OnLinkChanged;
            link.DownloadChanged -= this.OnLinkChanged;
            link.StatusChanged -= this.OnLinkChanged;
            link.ProgressChanged -= this.OnLinkProgressChanged;
        }

        private void OnLinkChanged(object sender, EventArgs e)
        {
            this.OnLinkChanged((UploadItemLink)sender);
        }

        private void OnLinkProgressChanged(object sender, EventArgs e)
        {
            this.OnProgressChanged();
        }
        #endregion

        private struct Pair
        {
            public UploadFile File;
            public UploadItemPoint Point;
        }
    }
}
